package storage

import (
	"sync"

	"github.com/ethereum/go-ethereum/common"
	"github.com/holiman/uint256"
)

//考虑到对于batchCache的访问是并发的，因此这里用sync.Map做Cache
//DataCache因为有两层，所以就额外声明了一个数据结构
//由于sync.Map的key，value都是interface，因此这里的备注标识一下Cache的数据类型，方便进行类型转换，并且考虑到interface类型转换可能只支持常见数据类型，所以这里只尝试用普通的数据类型做cache
type batchDataCache struct {
	RWMutex   sync.RWMutex
	DataCache dataCache
}

func (Bdc *batchDataCache) GetData(addr common.Address, hash common.Hash) (common.Hash, bool) {
	//读锁定
	Bdc.RWMutex.RLock()
	defer Bdc.RWMutex.RUnlock()
	state, ok := Bdc.DataCache[addr]
	if !ok {
		return common.Hash{}, false
	} else {
		if state == nil {
			return common.Hash{}, false
		}
		data, ok := (*state)[hash]
		if ok {
			return data, true
		} else {
			return common.Hash{}, false
		}
	}
}

func (Bdc *batchDataCache) SetData(addr common.Address, hash common.Hash, value common.Hash) {
	//写锁定
	Bdc.RWMutex.Lock()
	defer Bdc.RWMutex.Unlock()
	//首先判断该Address是否具有Cache
	state, ok := Bdc.DataCache[addr]
	if ok {
		(*state)[hash] = value
	} else {
		newCache := make(cache)
		Bdc.DataCache[addr] = &newCache
		(*Bdc.DataCache[addr])[hash] = value
	}
}

//BatchCache 记录一组Transaction所修改的所有世界状态的缓存
type BatchCache struct {
	DataCache *batchDataCache

	//key: commmon.Address
	//value: uint64
	BalanceCache sync.Map

	//key: common.Address
	//value: uint64
	NonceCache sync.Map

	//key: uint64
	//value:[]byte
	BlockHashCache sync.Map

	//key: common.Address
	//value: []byte
	CodeCache sync.Map

	//key: common.Address
	//value: []byte
	CodeHashCache sync.Map

	//key: common.Address
	//value: int
	CodeSizeCache sync.Map
}

func (Bc *BatchCache) GetBalance(addr common.Address) (*uint256.Int, bool) {
	if value, ok := Bc.BalanceCache.Load(addr); ok {
		//先把value转换成int
		tmp := value.(uint64)
		res := uint256.NewInt(tmp)
		return res, true
	} else {
		return nil, false
	}
}

func (Bc *BatchCache) SetBalance(addr common.Address, value *uint256.Int) {
	Bc.BalanceCache.Store(addr, value.Uint64()) //转成Uint64存进去
}

func (Bc *BatchCache) GetBlockHash(num uint64) (common.Hash, bool) {
	if value, ok := Bc.BlockHashCache.Load(num); ok {
		//先把value转成[]byte
		tmp := value.([]byte)
		res := common.BytesToHash(tmp)
		return res, true
	} else {
		return common.Hash{}, false
	}
}

func (Bc *BatchCache) SetBlockHash(num uint64, blockHash common.Hash) {
	Bc.BlockHashCache.Store(num, blockHash.Bytes())
}

func (Bc *BatchCache) GetCodeSize(addr common.Address) (int, bool) {
	if value, ok := Bc.CodeSizeCache.Load(addr); ok {
		res := value.(int)
		return res, true
	} else {
		return int(0), false
	}
}

func (Bc *BatchCache) SetCodeSize(addr common.Address, size int) {
	Bc.CodeSizeCache.Store(addr, size)
}

func (Bc *BatchCache) GetCode(addr common.Address) ([]byte, bool) {
	if value, ok := Bc.CodeSizeCache.Load(addr); ok {
		res := value.([]byte)
		return res, true
	} else {
		return nil, false
	}
}
func (Bc *BatchCache) SetCode(addr common.Address, code []byte) {
	Bc.CodeCache.Store(addr, code)
}

func (Bc *BatchCache) GetState(addr common.Address, hash common.Hash) (common.Hash, bool) {
	value, ok := Bc.DataCache.GetData(addr, hash)
	return value, ok
}

func (Bc *BatchCache) SetState(addr common.Address, hash common.Hash, value common.Hash) {
	Bc.DataCache.SetData(addr, hash, value)
}

func (Bc *BatchCache) GetNonce(addr common.Address) (uint64, bool) {
	if value, ok := Bc.NonceCache.Load(addr); ok {
		res := value.(uint64)
		return res, true
	} else {
		return uint64(0), false
	}
}

func (Bc *BatchCache) SetNonce(addr common.Address, nonce uint64) {
	Bc.NonceCache.Store(addr, nonce)
}
